package com.lifang.framework.aop.framework.support;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.StrUtil;
import com.lifang.framework.aop.aspectj.*;
import org.aopalliance.aop.Advice;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.reflect.AjType;
import org.aspectj.lang.reflect.AjTypeSystem;
import org.aspectj.lang.reflect.PerClauseKind;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * aspectJ配置工程
 */
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory {

    @Override
    public List<Advice> getAdvice(Class<?> clazz, String beanName) {
        AjType<?> ajType = AjTypeSystem.getAjType(clazz);
        if (!ajType.isAspect() || !Objects.equals(ajType.getPerClause().getKind(), PerClauseKind.SINGLETON)) {
            throw new UnsupportedOperationException("not support aspectJ form class " + clazz.getName());
        }
        List<Advice> adviceList = new ArrayList<>();
        for (Method method : clazz.getMethods()) {
            AspectJAnnotation<? extends Annotation> annotation = findAspectJAnnotationOnMethod(method);
            if (annotation == null) {
                continue;
            }
            AbstractAspectJAdvice advice;
            AspectJExpressionPointcut expressionPointcut = new AspectJExpressionPointcut(annotation.getPointcutExpression());
            switch (annotation.getAnnotationType()) {
                case AtAround:
                    advice = new AspectJAroundAdvice(method, expressionPointcut);
                    break;
                case AtBefore:
                    advice = new AspectJBeforeAdvice(method, expressionPointcut);
                    break;
                case AtAfter:
                    advice = new AspectJAfterAdvice(method, expressionPointcut);
                    break;
                case AtAfterReturning:
                    advice = new AspectJAfterReturningAdvice(method, expressionPointcut);
                    String returningName = AnnotationUtil.getAnnotationValue(method, AfterReturning.class, "returning");
                    if (StrUtil.isNotBlank(returningName)) {
                        advice.setReturningName(returningName);
                    }
                    break;
                case AtAfterThrowing:
                    advice = new AspectJAfterThrowingAdvice(method, expressionPointcut);
                    String throwingName = AnnotationUtil.getAnnotationValue(method, AfterThrowing.class, "throwing");
                    if (StrUtil.isNotBlank(throwingName)) {
                        advice.setThrowingName(throwingName);
                    }
                    break;
                default:
                    throw new UnsupportedOperationException(clazz.getName() + " has unsupported advice type on method: " + method.getName());
            }
            advice.calculateArgumentBindings();
            adviceList.add(advice);
        }
        return adviceList;
    }
}
